home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SoundMaker 2003 (Professional Edition)
/
SoundMaker 2003 - Professional Edition.iso
/
midi tool
/
midioxse.exe
/
DATA.1
/
ARPEGI.REX
next >
Wrap
OS/2 REXX Batch file
|
1999-03-27
|
15KB
|
597 lines
/* REXX: MIDI Arpeggiator */
/* Copyright (c) 1998 by Jamie O'Connell */
/* Designed to be used along with diverting of input */
signal on NOVALUE
call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
call SysLoadFuncs
/* ask for the channel byte split off */
qName = MOXSetQueueDataFormat( 'C' )
oldq = RxQueue( 'Set', qName )
/* MIDI-OX creates a semaphore with same name as queue */
sem = SysOpenEventSem( qName )
running = (sem <> 0)
/* Set up the arpeggiator object */
/* TEMP: Use defaults */
now = MoxGetSystemTime()
parse arg t r s o c
tempo = 120
if t <> '' then
tempo = t
resolution = 0.25 /* 16th note */
if r <> '' then
resolution = r
sustain = 2 /* stacatto */
if s <> '' then
sustain = s /* stacatto */
octaves = 2 /* 2 octaves */
if o <> '' then
octaves = o
cycle = 3 /* Up arpeggiator */
if c <> '' then
cycle = c
Arp = .arpeggiator~New( now, tempo, resolution, sustain, octaves, cycle )
say "Tempo (BPM):" tempo
say "Resolution:" Arp~ResText() "(" resolution ")"
say "Sustain:" Arp~SusText() "(" sustain ")"
say "Octaves:" octaves
say "Cycle:" Arp~CycleText() "(" cycle ")"
say "Arpegi Initialized..."
last = 0
/* Main loop */
do while running
someInput = (Queued() <> 0)
someChord = (Arp~IsEmpty() = .FALSE)
do while someInput | someChord
/* get current timestamp */
now = MoxGetSystemTime()
if someInput
then do
pull timestamp status chan data1 data2
/* Sent by MIDI-OX to signify end of program */
if timestamp = 'END_DATA'
then do
running = 0
leave
end
if status = 144 /* Note on */
then do
if data2 > 0
then do
Arp~Add( now, chan, data1, data2 )
end
else
status = 128 /* Note off (0 velocity) */
end
if status = 128 then
Arp~Remove( chan, data1 )
else if status <> 144 then /* assume we're diverted */
ret = MOXOutputMidiC( status, chan, data1, data2 )
end
someChord = (Arp~IsEmpty() = .FALSE)
if someChord
then do /* see if the queue head has expired yet */
Arp~MaybeTrigger( now )
end
someInput = (Queued() <> 0)
end
if running
then do
/* queue is empty: you could do other processing here */
ret = SysWaitEventSem( sem )
if ret <> 0
then do
running = .false
say "Error SysWaitEventSem Code:" ret
end
end
end
/* close up shop */
call SysDropFuncs
say "Arpegi Closing... "
exit 0
::requires "arrayx.cls"
/* ========================================================== */
::class Arpeggiator subclass Object
::method Init
expose now tempo resolution sustain octaves cycle noteAry,
noteOnQ noteOffQ playQ period duration seqCount
use arg now, tempo, resolution, sustain, octaves, cycle
noteAry = .arrayx~new( 32 )
noteOnQ = .queue~new
noteOffQ = .queue~new
playQ = .queue~new
period = self~CalcPeriod( tempo, resolution )
duration = self~CalcDuration( period, sustain )
seqCount = 0
return
/* ---------------------------------------------------------- */
::method now attribute
::method tempo attribute
::method resolution attribute
::method sustain attribute
::method octaves attribute
::method cycle attribute
::method period attribute
::method duration attribute
::method seqCount attribute
/* ---------------------------------------------------------- */
::method ResText
expose resolution
if resolution <= 0.0625 then
return "1/64th note"
if resolution <= 0.125 then
return "1/32nd note"
if resolution <= 0.25 then
return "1/16th note"
if resolution <= 0.5 then
return "1/8th note"
if resolution <= 1 then
return "1/4 note"
if resolution <= 2 then
return "1/2 note"
return "Whole note"
/* ---------------------------------------------------------- */
::method SusText
expose sustain
select
when sustain = 1 then
return "Very Short"
when sustain = 2 then
return "Staccato"
when sustain = 3 then
return "Legatto"
otherwise
return "Unknown"
end
return "Unknown"
/* ---------------------------------------------------------- */
::method CycleText
expose cycle
select
when cycle = 1 then
return "Up"
when cycle = 2 then
return "Down"
when cycle = 3 then
return "Up-down"
when sustain = 4 then
return "Down-up"
otherwise
return "Unknown"
end
return "Unknown"
/* ---------------------------------------------------------- */
::method UpdateParms
expose tempo resolution sustain octaves cycle period duration
use arg tempo, resolution, sustain, octaves, cycle
period = self~CalcPeriod( tempo, resolution )
duration = self~CalcDuration( period, sustain )
return
/* ---------------------------------------------------------- */
::method IsEmpty
expose noteAry
return noteAry~IsEmpty()
/* ---------------------------------------------------------- */
::method CalcPeriod
use arg bpm, nBeats
/* 60 sec/min * 1000 msec/sec / bbm * beats (fractional) */
dur = (60000 / bpm) * nBeats
return dur
/* ---------------------------------------------------------- */
::method CalcDuration
use arg len, susCode
/* susCode is 1=short, 2=stacatto, 3=legato (1/3, 2/3, 3/3) */
/* We shorten the period a few msecs beyond that */
dur = (len * susCode / 3) - 2
return dur
/* ---------------------------------------------------------- */
::method Add
expose now noteAry
use arg now, chan, note, veloc
evt = .noteEvent~New( chan, note, veloc, self~duration )
count = noteAry~HiBound()
inserted = .FALSE
do ii = 1 to count
if note < noteAry[ ii ]~note
then do
noteAry~Insert( evt, ii )
inserted = .TRUE
leave
end
end
if inserted <> .TRUE then
noteAry~Add( evt )
self~Retrigger( now )
return
/* ---------------------------------------------------------- */
::method Remove
expose noteAry noteOnQ noteOffQ playQ
use arg chan, note
/* do removal in reverse order so we don't miss any events when the */
/* indexes renumber */
count = noteAry~HiBound()
do ii = count to 1 by -1
if noteAry[ ii ]~note = note & noteAry[ ii ]~chan = chan
then do
id = noteAry[ ii ]~id
do jj = playQ~Items to 1 by -1
if playQ[ jj ]~index = id
then do
self~noteOff( chan, playQ[ jj ]~CalcPitch( note ) )
playQ~Remove( jj )
end
end
do jj = noteOnQ~Items to 1 by -1
if noteOnQ[ jj ]~index = id then
noteOnQ~Remove( jj )
end
do jj = noteOffQ~Items to 1 by -1
if noteOffQ[ jj ]~index = id then
noteOffQ~Remove( jj )
end
noteAry~Remove( ii )
end
end
return
/* ---------------------------------------------------------- */
::method playOff
expose noteAry playQ
do while playQ~Items > 0
evt = playQ~Pull()
if evt <> .NIL
then do
noteEvt = self~FindEvent( evt~Index )
if noteEvt <> .NIL
then do
self~noteOff( noteEvt~Chan, evt~CalcPitch( noteEvt~Note ) )
end
end
end
return
/* ---------------------------------------------------------- */
::method noteOff
use arg chan, note
ret = MOXOutputMidiC( 128, chan, note, 0 )
return
/* ---------------------------------------------------------- */
::method noteOn
use arg chan, note, veloc
ret = MOXOutputMidiC( 144, chan, note, veloc )
return
/* ---------------------------------------------------------- */
::method Retrigger
expose cycle noteAry noteOnQ noteOffQ
use arg now
self~playOff()
/* resequence note ID's */
count = noteAry~HiBound()
do ii = 1 to count
noteAry[ ii ]~id = ii
end
/* clear out queues */
do ii = noteOnQ~Items to 1 by -1
noteOnQ~Remove( ii )
end
do ii = noteOffQ~Items to 1 by -1
noteOffQ~Remove( ii )
end
trigger = now + 20
/* combo box choices (1 - 4) */
select
when cycle = 1 then
self~FillUp( trigger )
when cycle = 2 then
self~FillDown( trigger )
when cycle = 3 then
self~FillUpDown( trigger )
when cycle = 4 then
self~FillDownUp( trigger )
otherwise
say "Invalid Cycle:" cycle
end
return
/* ---------------------------------------------------------- */
::method FindEvent
expose noteAry
use arg index
do ii = (index + 1) to 1 by -1
if noteAry[ ii ] = .NIL then /* possible if index > hiBound */
iterate
if noteAry[ ii ]~id = index then
return noteAry[ ii ]
end
return .NIL
/* ---------------------------------------------------------- */
::method createEvents
expose noteAry noteOnQ noteOffQ
use arg idx, octv, trigtime
evt = .qEvent~New( idx, octv, trigtime )
noteOnQ~Queue( evt )
trigOff = noteAry[ idx ]~duration + trigtime
evt = .qEvent~New( idx, octv, trigOff )
noteOffQ~Queue( evt )
trigtime = trigtime + self~Period
return trigtime
/* ---------------------------------------------------------- */
::method FillUp
expose octaves seqCount noteAry
use arg now
trigger = now
count = noteAry~HiBound()
seqCount = 0
do ii = 1 to octaves
do jj = 1 to count
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
return
/* ---------------------------------------------------------- */
::method FillDown
expose octaves noteAry seqCount
use arg now
trigger = now
count = noteAry~HiBound()
seqCount = 0
do ii = octaves to 1 by -1
do jj = count to 1 by -1
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
return
/* ---------------------------------------------------------- */
::method FillUpDown
expose octaves noteAry seqCount
use arg now
trigger = now
count = noteAry~HiBound()
seqCount = 0
do ii = 1 to octaves
do jj = 1 to count
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
do ii = octaves to 1 by -1
do jj = count to 1 by -1
if ii = octaves & jj = count then
iterate /* don't play first note coming down */
if ii = 1 & jj = 1 then
iterate /* don't play last note coming down */
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
return
/* ---------------------------------------------------------- */
::method FillDownUp
expose octaves seqCount noteAry
use arg now
trigger = now
count = noteAry~HiBound()
seqCount = 0
do ii = octaves to 1 by -1
do jj = count to 1 by -1
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
do ii = 1 to octaves
do jj = 1 to count
if ii = octaves & jj = count then
iterate /* don't play last note going up */
if ii = 1 & jj = 1 then
iterate /* don't play first note going up */
trigger = self~createEvents( jj, ii, trigger )
seqCount = seqCount + 1
end
end
return
/* ---------------------------------------------------------- */
::method MaybeTrigger
expose noteOnQ noteOffQ playQ
use arg now
nextTime = self~period * self~seqCount
/* has a note off trigger expired? */
evt = noteOffQ~Peek()
if evt <> .NIL
then do
if evt~trigger <= now
then do
trgEvt = self~FindEvent( evt~Index )
if trgEvt <> .NIL
then do
pitch = evt~CalcPitch( trgEvt~Note )
self~noteOff( trgEvt~chan, pitch )
do ii = playQ~Items to 1 by -1
if playQ[ ii ]~index = evt~Index then
playQ~Remove( ii )
end
evt = noteOffQ~Pull()
evt~trigger = evt~trigger + nextTime
noteOffQ~Queue( evt )
end
end
end
/* has a note on trigger expired? */
evt = noteOnQ~Peek()
if evt <> .NIL
then do
if evt~trigger <= now
then do
trgEvt = self~FindEvent( evt~Index )
if trgEvt <> .NIL
then do
pitch = evt~CalcPitch( trgEvt~Note )
self~noteOn( trgEvt~chan, pitch, trgEvt~velocity )
evt = noteOnQ~Pull()
newEvt = .qEvent~New( evt~Index, evt~octMult, evt~trigger )
playQ~Queue( newEvt )
evt~trigger = evt~trigger + nextTime
noteOnQ~Queue( evt )
end
end
end
return
/* ========================================================== */
::class noteEvent subclass Object
::method Init
expose chan note velocity duration id
use arg chan, note, velocity, duration
id = -1 /* must be filled later */
return
::method id attribute
::method chan attribute
::method note attribute
::method velocity attribute
::method duration attribute
/* ========================================================== */
::class qEvent subclass Object
::method Init
expose index octMult trigger
use arg index, octMult, trigger
return
/* ---------------------------------------------------------- */
::method index attribute
::method octMult attribute
::method trigger attribute
/* ---------------------------------------------------------- */
::method CalcPitch
expose octMult
use arg note
pitch = (octMult - 1) * 12 + note
return pitch